import pandas as pd
import numpy as np
from typing import Dict, List, Any, Optional
from datetime import datetime

def calculate_returns(
    portfolio_values: pd.Series,
    benchmark_values: Optional[pd.Series] = None
) -> Dict[str, pd.Series]:
    """
    Calculate strategy and benchmark returns.
    
    Args:
        portfolio_values: Series of portfolio values
        benchmark_values: Optional series of benchmark values
        
    Returns:
        Dictionary containing returns series
    """
    strategy_returns = portfolio_values.pct_change()
    
    result = {'strategy': strategy_returns}
    
    if benchmark_values is not None:
        benchmark_returns = benchmark_values.pct_change()
        result['benchmark'] = benchmark_returns
        
    return result

def calculate_statistics(returns: pd.Series) -> Dict[str, float]:
    """
    Calculate performance statistics.
    
    Args:
        returns: Series of returns
        
    Returns:
        Dictionary containing performance metrics
    """
    # Annualization factor
    ann_factor = 252 if len(returns) >= 252 else len(returns)
    
    # Basic statistics
    total_return = (1 + returns).prod() - 1
    ann_return = (1 + total_return) ** (ann_factor / len(returns)) - 1
    ann_volatility = returns.std() * np.sqrt(ann_factor)
    
    # Sharpe Ratio (assuming risk-free rate = 0)
    sharpe_ratio = ann_return / ann_volatility if ann_volatility != 0 else 0
    
    # Maximum Drawdown
    cum_returns = (1 + returns).cumprod()
    rolling_max = cum_returns.expanding().max()
    drawdowns = cum_returns / rolling_max - 1
    max_drawdown = drawdowns.min()
    
    # Win Rate
    win_rate = (returns > 0).mean()
    
    return {
        'total_return': total_return,
        'annualized_return': ann_return,
        'annualized_volatility': ann_volatility,
        'sharpe_ratio': sharpe_ratio,
        'max_drawdown': max_drawdown,
        'win_rate': win_rate
    }

def calculate_drawdowns(returns: pd.Series) -> pd.Series:
    """
    Calculate drawdown series.
    
    Args:
        returns: Series of returns
        
    Returns:
        Series of drawdowns
    """
    cum_returns = (1 + returns).cumprod()
    rolling_max = cum_returns.expanding().max()
    drawdowns = cum_returns / rolling_max - 1
    return drawdowns

def calculate_rolling_statistics(
    returns: pd.Series,
    window: int = 252
) -> Dict[str, pd.Series]:
    """
    Calculate rolling performance statistics.
    
    Args:
        returns: Series of returns
        window: Rolling window size
        
    Returns:
        Dictionary containing rolling statistics
    """
    rolling_return = returns.rolling(window).apply(
        lambda x: (1 + x).prod() - 1
    )
    
    rolling_vol = returns.rolling(window).std() * np.sqrt(window)
    
    rolling_sharpe = (
        rolling_return / rolling_vol
        if rolling_vol.any() != 0
        else pd.Series(0, index=returns.index)
    )
    
    return {
        'rolling_return': rolling_return,
        'rolling_volatility': rolling_vol,
        'rolling_sharpe': rolling_sharpe
    }

def analyze_trades(trades: List[Dict[str, Any]]) -> Dict[str, Any]:
    """
    Analyze trading performance.
    
    Args:
        trades: List of trade dictionaries
        
    Returns:
        Dictionary containing trade analysis
    """
    if not trades:
        return {}
        
    df = pd.DataFrame(trades)
    
    # Calculate trade returns
    df['return'] = np.where(
        df['side'] == 'buy',
        -df['total_cost'],
        df['total_cost']
    )
    
    # Calculate statistics
    total_trades = len(trades)
    winning_trades = len(df[df['return'] > 0])
    losing_trades = len(df[df['return'] <= 0])
    
    win_rate = winning_trades / total_trades if total_trades > 0 else 0
    
    avg_win = df[df['return'] > 0]['return'].mean() if winning_trades > 0 else 0
    avg_loss = df[df['return'] <= 0]['return'].mean() if losing_trades > 0 else 0
    
    profit_factor = (
        abs(avg_win * winning_trades) / abs(avg_loss * losing_trades)
        if losing_trades > 0 and avg_loss != 0
        else float('inf')
    )
    
    return {
        'total_trades': total_trades,
        'winning_trades': winning_trades,
        'losing_trades': losing_trades,
        'win_rate': win_rate,
        'average_win': avg_win,
        'average_loss': avg_loss,
        'profit_factor': profit_factor,
        'total_commission': df['commission'].sum()
    }

def generate_performance_report(
    portfolio_values: pd.Series,
    trades: List[Dict[str, Any]],
    benchmark_values: Optional[pd.Series] = None
) -> Dict[str, Any]:
    """
    Generate comprehensive performance report.
    
    Args:
        portfolio_values: Series of portfolio values
        trades: List of trade dictionaries
        benchmark_values: Optional series of benchmark values
        
    Returns:
        Dictionary containing performance report
    """
    # Calculate returns
    returns_dict = calculate_returns(portfolio_values, benchmark_values)
    strategy_returns = returns_dict['strategy']
    
    # Calculate statistics
    stats = calculate_statistics(strategy_returns)
    
    # Calculate rolling statistics
    rolling_stats = calculate_rolling_statistics(strategy_returns)
    
    # Analyze trades
    trade_analysis = analyze_trades(trades)
    
    # Combine all metrics
    report = {
        'portfolio_statistics': stats,
        'rolling_statistics': rolling_stats,
        'trade_analysis': trade_analysis
    }
    
    # Add benchmark comparison if available
    if benchmark_values is not None:
        benchmark_returns = returns_dict['benchmark']
        benchmark_stats = calculate_statistics(benchmark_returns)
        report['benchmark_statistics'] = benchmark_stats
        
        # Calculate tracking error and information ratio
        tracking_error = (strategy_returns - benchmark_returns).std() * np.sqrt(252)
        information_ratio = (
            (stats['annualized_return'] - benchmark_stats['annualized_return'])
            / tracking_error if tracking_error != 0 else 0
        )
        
        report['relative_statistics'] = {
            'tracking_error': tracking_error,
            'information_ratio': information_ratio
        }
        
    return report
